import * as monaco from 'monaco-editor';
import { languages as MonacoLanguages } from 'monaco-editor';
import { IEditorCompleterProps, IEditorLanguage } from './CodeEditorConfig';
const {
  language: sqlLanguage,
} = require('monaco-editor/esm/vs/basic-languages/sql/sql.js');

const { builtinFunctions, keywords, operators } = sqlLanguage;

const dateVariable: IEditorCompleterProps[] = [
  {
    value: '${dt:format("0H", "HH")}',
    meta: '当前小时ID',
  },
  {
    value: '${dt:format("-1H", "HH")}',
    meta: '上一小时ID',
  },
  {
    value: '${dt:format("0d", "yyyy-MM-dd")}',
    meta: '今天',
  },
  {
    value: '${dt:format("-1d", "yyyy-MM-dd")}',
    meta: '昨天',
  },
  {
    value: '${dt:format("+6d-1w", "ww")}',
    meta: '本周ID',
  },
  {
    value: '${dt:format("+6d-2w", "ww")}',
    meta: '上周ID',
  },
  {
    value: '${dt:format("+6d-1w+0wB+1d", "yyyy-MM-dd")}',
    meta: '本周开始日期',
  },
  {
    value: '${dt:format("+6d-1w+0wE+1d", "yyyy-MM-dd")}',
    meta: '本周结束日期',
  },

  {
    value: '${dt:format("0M", "MM")}',
    meta: '本月ID',
  },
  {
    value: '${dt:format("-1M", "MM")}',
    meta: '上月ID',
  },
  {
    value: '${dt:format("0MB", "yyyy-MM-dd")}',
    meta: '本月开始日期',
  },
  {
    value: '${dt:format("0ME", "yyyy-MM-dd")}',
    meta: '本月结束日期',
  },
];

/**
 * @description monaco-editor 自定义代码补全建议
 */
export default class RegisterCompletion {
  _language: IEditorLanguage;
  _hintData: IEditorCompleterProps[];
  editorCompletion: monaco.IDisposable | undefined;
  editorHover: monaco.IDisposable | undefined;
  // 时间变量表达式，详情请查阅时间参数设置工具：https://km.vivo.xyz/pages/viewpage.action?pageId=14510026
  customKeywords: string[];
  triggerCharacters: string[];

  constructor(language: IEditorLanguage, hintData: IEditorCompleterProps[]) {
    this._language = language;
    this._hintData = hintData;
    this.editorCompletion = undefined;
    this.editorHover = undefined;
    this.customKeywords = ['$'];
    this.triggerCharacters = [];
    this.registerCompletion();
  }

  /**
   * @description 销毁
   */
  dispose() {
    this.editorCompletion?.dispose?.();
  }

  /**
   * @description 为编辑器注入变量提示
   * @returns dispose
   */
  registerCompletion() {
    this.editorCompletion = MonacoLanguages.registerCompletionItemProvider(
      this._language,
      {
        triggerCharacters: ['.', '$', ...keywords, ...this.triggerCharacters],

        provideCompletionItems: async (model, position) => {
          let suggestions: any = [];
          const { lineNumber, column } = position;

          // 光标前文本
          const textBeforePointer = model.getValueInRange({
            startLineNumber: lineNumber,
            startColumn: 0,
            endLineNumber: lineNumber,
            endColumn: column,
          });

          const textBeforePointerMulti = model.getValueInRange({
            startLineNumber: 1,
            startColumn: 0,
            endLineNumber: lineNumber,
            endColumn: column,
          });

          // 光标后文本
          // const textAfterPointer = model.getValueInRange({
          //   startLineNumber: lineNumber,
          //   startColumn: column,
          //   endLineNumber: lineNumber,
          //   endColumn: model.getLineMaxColumn(model.getLineCount())
          // });

          const textAfterPointerMulti = model.getValueInRange({
            startLineNumber: lineNumber,
            startColumn: column,
            endLineNumber: model.getLineCount(),
            endColumn: model.getLineMaxColumn(model.getLineCount()),
          });

          /* 正在编辑的单词 */
          const tokens = textBeforePointer.trim().split(/\s+/);
          // 获取最后一段非空字符串
          const lastToken = tokens[tokens?.length - 1].toLowerCase();

          const sqlKeywordList = [
            'select',
            'where',
            'order by',
            'group by',
            'by',
            'and',
            'or',
            'having',
            'distinct',
            'on',
          ];

          /* .结尾 */
          // <库名>.<表名> || <别名>.<字段>
          if (lastToken.endsWith('.')) {
            // 去掉点后的字符串
            const tokenNoDot = lastToken.slice(0, lastToken.length - 1);

            // 以';'拆分sql语句，获取表名和表别名
            const sqlArr = textBeforePointerMulti.split(';');
            const sqlText =
              sqlArr[sqlArr.length - 1] + textAfterPointerMulti.split(';')[0];
            const tableInfoList = this.getTableNameAndTableAlias(sqlText);

            //
            const lastWord = tokenNoDot.replace(/^.*[=|,]/g, '')?.trim();

            if (this._hintData.find((item) => item.caption === lastWord)) {
              // <库名>.<表名>联想
              suggestions = [...this.getTableSuggest(lastWord)];
            } else if (tableInfoList?.length > 0) {
              const currentTable = tableInfoList.find(
                (item: any) =>
                  item.tableAlias === lastWord || item.tableName === lastWord,
              );
              // <别名>.<字段>联想
              if (currentTable) {
                const words = currentTable.tableName.split('.');
                const [dbName, tblName] = words;

                suggestions = this.getColumnSuggest(dbName, tblName);
              }
            }
          } else if (
            lastToken === 'from' ||
            lastToken === 'join' ||
            /(from|join)\s+.*?\s?,\s*$/.test(
              textBeforePointer.replace(/.*?\(/gm, '').toLowerCase(),
            )
          ) {
            suggestions = this.getDBSuggest();
            // 字段联想
          } else if (
            sqlKeywordList.includes(lastToken.replace(/.*?\(/gm, '')) ||
            new RegExp(
              '(' + sqlKeywordList.join('|') + ')\\s+.*?\\s?,\\s*$',
            ).test(textBeforePointer.toLowerCase()) ||
            (lastToken.endsWith('.') &&
              !this._hintData.find((item) => `${item.caption}.` === lastToken))
          ) {
            suggestions = this.getColumnSuggest(lastToken, 'test');
          } else if (this.customKeywords.includes(lastToken)) {
            // 自定义字段联想
            suggestions = this.getCustomSuggest(lastToken.startsWith('$'));

            // 默认联想
          } else if (/\$\{[^{].*?\}?/gi.test(lastToken)) {
            suggestions = this.getCustomSuggest(true);
          } else {
            suggestions = [...this.getSQLSuggest(), ...this.getDBSuggest()];
          }

          return {
            suggestions,
          };
        },
        // 光标选中当前自动补全item时触发动作，一般情况下无需处理
        resolveCompletionItem(item, token) {
          return null;
        },
      },
    );
  }

  /**
   * 为编辑器注入鼠标悬浮提示
   */
  registerHover() {
    this.editorHover = monaco.languages.registerHoverProvider(this._language, {
      provideHover: (model, position) => {
        const contents: any = [];

        // 自定义变量提示规则
        return {
          contents,
        };
      },
    });
  }

  createSuggest(
    data: IEditorCompleterProps[] | string[],
    kind: MonacoLanguages.CompletionItemKind,
    meta?: string,
  ) {
    return data.map((item) => {
      if (typeof item === 'string') {
        return {
          label: item, // 显示的label
          kind,
          insertText: item, // 选择后插入的value
          detail: meta, // 描述
        };
      }

      return {
        label: item?.caption || item.value, // 显示的label
        kind,
        insertText: item.value.startsWith('$')
          ? item.value.slice(1, item.value.length)
          : item.value, // 选择后插入的value
        detail: meta ?? item?.meta, // 描述
      };
    });
  }

  // 获取 SQL 语法提示
  getSQLSuggest() {
    return this.createSuggest(
      [...builtinFunctions, ...keywords, ...operators],
      MonacoLanguages.CompletionItemKind.Enum,
    );
  }

  // 数据库名（hintData 的 key 值）
  getDBSuggest() {
    let dbs = this._hintData ?? [];

    return this.createSuggest(
      dbs,
      MonacoLanguages.CompletionItemKind.Constant,
      'Database',
    );
  }

  /**
   * 获取sql中所有的表名和别名* @param {*} sqlText SQL字符串
   */
  getTableNameAndTableAlias(sqlText: string) {
    const regTableAliasFrom =
      /(^|(\s+))from\s+([^\s]+(\s+|(\s+as\s+))[^\s]+(\s+|,)\s*)+(\s+(where|left|right|full|join|inner|union))?/gi;
    const regTableAliasJoin =
      /(^|(\s+))join\s+([^\s]+)\s+(as\s+)?([^\s]+)\s+on/gi;

    const regTableAliasFromList = sqlText.match(regTableAliasFrom)
      ? sqlText.match(regTableAliasFrom)
      : [];
    const regTableAliasJoinList = sqlText.match(regTableAliasJoin)
      ? sqlText.match(regTableAliasJoin)
      : [];
    const strList = [
      ...(regTableAliasFromList ?? []).map((item) =>
        item
          .replace(/(^|(\s+))from\s+/gi, '')
          .replace(
            /\s+(where|left|right|full|join|inner|union)((\s+.*?$)|$)/gi,
            '',
          )
          .replace(/s+as\s+/gi, ' ')
          .trim(),
      ),
      ...(regTableAliasJoinList ?? []).map((item) =>
        item
          .replace(/(^|(\s+))join\s+/gi, '')
          .replace(/\s+on((\s+.*?$)|$)/, '')
          .replace(/\s+as\s+/gi, ' ')
          .trim(),
      ),
    ];

    const tableList: any = [];

    strList.map((tableAndAlias) => {
      tableAndAlias.split(/\s*,\s*/).forEach((item) => {
        const [tableName, tableAlias] = item.trim().split(/(?:\s+as\s+)|\s+/gi);
        tableList.push({
          tableName,
          tableAlias,
        });
      });
    });

    return tableList;
  }

  // 表名（hintData 的 value 值）
  getTableSuggest(dbName: string) {
    const db = this._hintData.find(
      (item) => (item?.caption || item.value) === dbName,
    );
    const tables = db?.children ?? [];

    return this.createSuggest(
      tables,
      MonacoLanguages.CompletionItemKind.Constant,
      'Table',
    );
  }

  // 字段名（hintData 的 value 值）
  getColumnSuggest(dbName: string, tblName: string) {
    const db = this._hintData.find(
      (item) => (item?.caption || item.value) === dbName,
    );
    const tbl = db?.children?.find(
      (item) => (item?.caption || item.value) === tblName,
    );

    const columns = tbl?.children ?? [];

    return this.createSuggest(
      columns,
      MonacoLanguages.CompletionItemKind.Constant,
      'Column',
    );
  }

  getCustomSuggest(flag: boolean) {
    let result: string[] = [];

    if (flag) {
      return this.createSuggest(
        dateVariable,
        MonacoLanguages.CompletionItemKind.Keyword,
      );
    }

    return result;
  }
}
